import { isNaN, isArray, isString, isFunction } from '../shared'

/**
 * 创建一个具有唯一array值的数组，每个值不包含在其他给定的数组中。
 * （注：即创建一个新数组，这个数组中的值，为第一个数字（array 参数）排除了给定数组中的值。）
 * 该方法使用SameValueZero做相等比较。结果值的顺序是由第一个数组中的顺序确定。
 * @param array - 要检查的数组。
 * @param values - 排除的值。
 * @returns 返回一个过滤值后的新数组。
 * @example
 * difference([3, 2, 1], [4, 2]);
 * // => [3, 1]
 */
export function difference(array: any[], values: any[]): any[] {
    if (!isArray(array) || !isArray(values)) return [];

    const valuesSet = new Set(values);
    let hasNaN = false;

    for (const value of values) {
        if (isNaN(value)) {
            hasNaN = true;
            break;
        }
    }

    return array.filter(item => {
        if (isNaN(item)) return !hasNaN;
        return !valuesSet.has(item);
    });
}

/**
 * 这个方法类似_.difference ，除了它接受一个 iteratee （注：迭代器）， 调用array 和 values 中的每个元素以产生比较的标准。 
 * 结果值是从第一数组中选择。iteratee 会调用一个参数：(value)。（注：首先使用迭代器分别迭代array 和 values中的每个元素，返回的值作为比较值）。
 * @param array - 要检查的数组。
 * @param values - 排除的值。
 * @returns 返回一个过滤值后的新数组。
 * @example
 * differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);
 * // => [3.1, 1.3]
 * The `_.property` iteratee shorthand.
 * differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
 * // => [{ 'x': 2 }]
 */
export function differenceBy(array: any[], values: any[], identity: any): any[] {
    if (!array) return [];
    
    const getValue = (item: any): any => {
        
        if (isFunction(identity)) return identity(item);
        else if (isString(identity)) return item[identity];

        return item;
    };

    const valuesSet = new Set();
    for (const value of values) {
        valuesSet.add(getValue(value));
    }

    return array.filter(item => !valuesSet.has(getValue(item)));
}

/**
 * 这个方法类似_.difference ，除了它接受一个 comparator （注：比较器），它调用比较array，values中的元素。 
 * 结果值是从第一数组中选择。comparator 调用参数有两个：(arrVal, othVal)。
 * @param array - 要检查的数组。
 * @param values - 排除的值。
 * @param comparator - comparator 调用每个元素。
 * @returns 返回一个过滤值后的新数组。
 * @example
 * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
 * differenceWith(objects, [{ 'x': 1, 'y': 2 }], isEqual);
 * // => [{ 'x': 2, 'y': 1 }]
 */
export function differenceWith<T>(
    array: T[], values: T[], 
    comparator: (arrVal: T, othVal: T) => boolean
): T[] {
    if (!isArray(array)) return [];
    if (!isArray(values) || !isFunction(comparator)) return array.slice();

    return array.filter(arrItem => {
        return !values.some(valueItem => comparator(arrItem, valueItem));
    });
}